ποΈGitΠ―ΡΠ°ποΈ
core/takserver/README.md 4d4070c8e1ed73a56ee2f054300a0f62e3fe2e62 (4d4070c8) Text, 12.62 KB
T383838:core:takserver
Overview
The T383838:core:takserver module implements the Meshtastic β TAK (Team Awareness Kit) bridge. It embeds an mTLS TCP server (port 8089) compatible with ATAK (Android), iTAK (iOS), and WinTAK clients, enabling mesh-networked position sharing and GeoChat with TAK-enabled devices.
Targets: Android Β· JVM (Desktop) Β· iOS β fully multiplatform with T383838expect/T383838actual splits for compression, file I/O, and the TCP server itself.
Key Responsibilities
β’ Serve an mTLS TCP listener (port 8089) compatible with the CoT (Cursor-on-Target) protocol
β’ Convert Meshtastic protobuf packets (T383838TAKPacketV2) to CoT XML events and vice versa
β’ Generate ATAK Data Package T383838.zip exports (team contacts, map overlays)
β’ Compress CoT payloads using Zstd (TAK SDK format) with T383838expect/T383838actual platform implementations
β’ Buffer up to 50 CoT messages for 5 minutes when no TAK clients are connected; drain on reconnect
β’ Provide Crowdin-localised TAK preference XML for ATAK client provisioning
Source Structure
T282828
src/
βββ commonMain/kotlin/org/meshtastic/core/takserver/
β βββ TAKServer.kt β interface + expect createTAKServer()
β βββ TAKServerManager.kt β interface + TAKServerManagerImpl (offline queue)
β βββ TAKMeshIntegration.kt β bridges mesh service β TAK server
β βββ CoTConversion.kt β Position/User β CoTMessage extension fns
β βββ CoTXml.kt / CoTXmlParser.kt / CoTXmlFrameBuffer.kt
β βββ CoTXmlDataClasses.kt
β βββ CoTDetailStripper.kt
β βββ TAKModels.kt β CoTMessage, TAKClientInfo, TAKConnectionEvent
β βββ TAKPacketConversion.kt
β βββ TAKPacketV2Conversion.kt
β βββ TAKDefaults.kt
β βββ TAKDataPackageGenerator.kt
β βββ RouteDataPackageGenerator.kt
β βββ TAKPrefXmlDataClasses.kt
β βββ TakV2TypeMapper.kt
β βββ TakConversionHelpers.kt
β βββ XmlUtils.kt
β βββ AtakFileWriter.kt β expect
β βββ TakSdkCompressor.kt β expect (Zstd TAK-SDK frame)
β βββ TakV2Compressor.kt β expect (Zstd TAKPacketV2 frame)
β βββ ZipArchiver.kt β expect
β βββ TakFixtureLoader.kt β expect (test fixtures)
β βββ TakMeshTestRunner.kt
β βββ di/
β βββ CoreTakServerModule.kt
βββ jvmAndroidMain/kotlin/ β actual TAKServerJvm, TAKClientConnection, TakCertLoader
βββ androidMain/kotlin/ β actual AtakFileWriter (Android)
βββ jvmMain/kotlin/ β actual AtakFileWriter (Desktop), XML pull-parser
βββ iosMain/kotlin/ β actual TAKServerIos, actual compression impls
Notable APIs
T383838TAKServer (interface)
T282828
Tff7b72interface T56d364TAKServer Tb4b4b4{
Tff7b72val Te6edf3connectionCountTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657IntTff7b72>
Tff7b72var Te6edf3onMessageTb4b4b4: Tb4b4b4(Tb4b4b4(Te6edf3CoTMessageTb4b4b4, Te6edf3TAKClientInfo?Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tff7b72?
Tff7b72var Te6edf3onClientConnectedTb4b4b4: Tb4b4b4(Tb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)Tff7b72?
Tff7b72suspend Tff7b72fun Td2a8ffstartTb4b4b4(Te6edf3scopeTb4b4b4: Te6edf3CoroutineScopeTb4b4b4)Tb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72>
Tff7b72fun Td2a8ffstopTb4b4b4(Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffbroadcastTb4b4b4(Te6edf3cotMessageTb4b4b4: Te6edf3CoTMessageTb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffbroadcastRawXmlTb4b4b4(Te6edf3xmlTb4b4b4: Tffa657StringTb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffhasConnectionsTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Boolean
Tb4b4b4}
The mTLS listener binds on port 8089 using a bundled T383838server.p12 / T383838ca.pem identity, compatible with the ATAK Data Package provisioning flow.
T383838TAKServerManager (interface)
T282828
Tff7b72interface T56d364TAKServerManager Tb4b4b4{
Tff7b72val Te6edf3isRunningTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657BooleanTff7b72>
Tff7b72val Te6edf3connectionCountTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657IntTff7b72>
Tff7b72val Te6edf3inboundMessagesTb4b4b4: Te6edf3SharedFlowTff7b72<Te6edf3InboundCoTMessageTff7b72>
Tff7b72suspend Tff7b72fun Td2a8ffstartTb4b4b4(Te6edf3scopeTb4b4b4: Te6edf3CoroutineScopeTb4b4b4)
Tff7b72fun Td2a8ffstopTb4b4b4(Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffbroadcastTb4b4b4(Te6edf3cotMessageTb4b4b4: Te6edf3CoTMessageTb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffbroadcastRawXmlTb4b4b4(Te6edf3xmlTb4b4b4: Tffa657StringTb4b4b4)
Tb4b4b4}
T383838TAKServerManagerImpl adds an offline queue: buffers up to 50 CoT messages for 5 minutes when no clients are connected and drains them automatically on the next T383838onClientConnected callback.
T383838CoTMessage
T282828
Tf0883e@Serializable
Tff7b72data Tff7b72class T56d364CoTMessageTb4b4b4(
Tff7b72val Te6edf3uidTb4b4b4: Tffa657StringTb4b4b4,
Tff7b72val Te6edf3typeTb4b4b4: Tffa657StringTb4b4b4, T8b949e// e.g. "a-f-G-U-C" (friendly ground unit)
Tff7b72val Te6edf3timeTb4b4b4: Te6edf3InstantTb4b4b4,
Tff7b72val Te6edf3latTb4b4b4: Tffa657DoubleTb4b4b4, Tff7b72val Te6edf3lonTb4b4b4: Tffa657DoubleTb4b4b4, Tff7b72val Te6edf3haeTb4b4b4: Tffa657DoubleTb4b4b4,
Tff7b72val Te6edf3contactTb4b4b4: Te6edf3CoTContact?Tb4b4b4,
Tff7b72val Te6edf3groupTb4b4b4: Te6edf3CoTGroup?Tb4b4b4,
Tff7b72val Te6edf3trackTb4b4b4: Te6edf3CoTTrack?Tb4b4b4,
Tff7b72val Te6edf3chatTb4b4b4: Te6edf3CoTChat?Tb4b4b4,
Tff7b72val Te6edf3remarksTb4b4b4: Tffa657String?Tb4b4b4,
T8b949e// ...
Tb4b4b4)
T8b949e// Factory helpers
Te6edf3CoTMessageTb4b4b4.Te6edf3pliTb4b4b4(Te6edf3uidTb4b4b4, Te6edf3callsignTb4b4b4, Te6edf3latTb4b4b4, Te6edf3lonTb4b4b4, Tb4b4b4.Tb4b4b4.Tb4b4b4.Tb4b4b4) T8b949e// Position Location Information
Te6edf3CoTMessageTb4b4b4.Te6edf3chatTb4b4b4(Te6edf3senderUidTb4b4b4, Te6edf3callsignTb4b4b4, Te6edf3messageTb4b4b4, Te6edf3chatroomTb4b4b4)
CoT Conversion
T282828
T8b949e// Meshtastic proto β CoT
Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3PositionTb4b4b4.Te6edf3toCoTMessageTb4b4b4(Te6edf3uidTb4b4b4, Te6edf3callsignTb4b4b4, Te6edf3teamTb4b4b4, Te6edf3roleTb4b4b4, Te6edf3batteryTb4b4b4)Tb4b4b4: Te6edf3CoTMessage
Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3UserTb4b4b4.Te6edf3toCoTMessageTb4b4b4(Te6edf3positionTb4b4b4, Te6edf3teamTb4b4b4, Te6edf3roleTb4b4b4, Te6edf3batteryTb4b4b4)Tb4b4b4: Te6edf3CoTMessage
Dependency Graph
T282828
core:takserver
βββ api β core:repository (exported)
βββ core:common, core:di, core:model, org.meshtastic:protobufs (Maven)
βββ okio, kotlinx.serialization.json
βββ xmlutil-core, xmlutil-serialization
βββ ktor-client-core, ktor-network (TCP socket)
βββ kotlinx.datetime, kermit (zstd rides on the SDK's transitive kzstd)
Local TAK Server Feature
The Local TAK Server can be enabled from the app's Settings screen. When running, ATAK/iTAK clients on the same network can connect to T383838<device-ip>:8089 and their position reports are automatically bridged onto the mesh. CoT arriving from the mesh on ports 72/78 is forwarded to every connected TAK client.
Mesh to CoT (node contacts)
Separately opt-in (T383838TakPrefs.isMeshToCotEnabled, default off, shown as "Mesh to CoT Converter" under the server toggle). When enabled alongside the server, T383838MeshToCotBroadcaster synthesizes a CoT contact for each node in the node database so regular Meshtastic nodes appear on the ATAK map without the legacy Meshtastic TAK Plugin β which cannot work at all since the AIDL API was removed in app 2.8.0.
Nodes qualify when they have identified themselves, were heard inside the online window (2 h), and hold a valid position; the local node is excluded because ATAK renders it as self.
Output is aligned against Meshtastic-Apple's T383838TAKMeshtasticBridge.createCoTFromNode (verified by reading that source, not inferred) so the same physical node presents identically on both platforms:
βββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ¬ββββββββββββββ
β Field β Value β Notes β
βββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββ€
β T383838uid β T383838MESHTASTIC-%08X β Upper-caseβ¦ β
β T383838callsign β T383838SHORT - Long Name β Falls backβ¦ β
β team / role β T383838Green / T383838Team Member β Remote nodβ¦ β
β stale β 15 min β Paired witβ¦ β
β T383838remarks β T383838Battery β¦ | Voltage β¦ | Chan Util β¦ | Air Util Tx β¦ | RSSI β¦ | SNR β¦ β Labels, orβ¦ β
βββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ΄ββββββββββββββ
Two deliberate divergences from Apple, both in T383838remarks:
β’ Zero is reported, not suppressed. Apple gates each field on a non-zero value (T383838if voltage > 0, T383838if rssi != 0, β¦) and substitutes 100% for an unreported battery. Here, absence is detected via nullability and the SNR/RSSI sentinels instead β 0 dB SNR and 0 dBm RSSI are real measurements, and 0% battery is precisely the reading an operator needs to see rather than have hidden.
β’ T383838Air Util Tx is additive β no Apple counterpart.
T383838Node.validPosition (the repo-wide helper) also requires both coordinates non-zero and in range, where Apple accepts either being non-zero; a node sitting exactly on the equator or prime meridian is therefore dropped here. Kept for consistency with every other position filter in the codebase.
Nothing on this path crosses the mesh, so none of it is subject to the LoRa MTU or the TAKPacket wire format. Three behaviours are load-bearing: broadcasts are suppressed while no client is attached (they would otherwise evict real mesh CoT from the 50-entry offline queue), a connecting client triggers a full replay, and every node is re-sent periodically so stationary markers do not expire at T383838MESH_NODE_STALE_MINUTES.
TAKPacket-SDK consumer & version-bump playbook
This module consumes the external TAKPacket-SDK (T383838org.meshtastic:takpacket-sdk, KMP since 0.7.0; pinned as T383838takpacket-sdk in T383838gradle/libs.versions.toml, currently 0.8.0) for the V2 wire format. The SDK does CoT-XML β T383838TAKPacketV2 β zstd-compressed bytes; it owns the dictionaries and the schema. The T383838TAKPacketV2 proto types themselves come from the T383838org.meshtastic:protobufs Maven artifact (pinned as T383838meshtastic-protobufs, api()-exported by T383838:core:model).
Two V2 wire paths β keep both in mind when the SDK changes:
β’ Path A (primary, SDK-delegated): T383838TakSdkCompressor / T383838TakV2Compressor call the SDK's parser/builder/compressor. This path is insulated from proto field renames as long as the SDK and T383838meshtastic-protobufs versions are bumped together.
β’ Path B (fallback): T383838TAKPacketV2Conversion.kt builds and reads the Wire-generated T383838TAKPacketV2 directly (SDK-failure send fallback; iOS receive stub). It references proto fields by name, so it breaks at compile time on any schema change and must be updated in lockstep.
When bumping to a new (wire-breaking) SDK version:
1. T383838gradle/libs.versions.toml β bump T383838takpacket-sdk (and, if the schema moved, T383838meshtastic-protobufs to the matching protobufs release).
2. Leave T383838:core:model's exclude block intact (T383838core/model/build.gradle.kts): the SDK still declares a transitive, older T383838org.meshtastic:protobufs pin, so T383838:core:model api()-exports the SDK with T383838exclude(group = "org.meshtastic", module = "protobufs" / "protobufs-jvm" / "protobufs-android") β that keeps the app's single protobufs version authoritative and prevents duplicate-class / proto-ABI breakage. (The T383838.toString() string-notation there is load-bearing: catalog dependencies are immutable, so T383838exclude {} only works on the string copy.)
3. Update Path B (T383838TAKPacketV2Conversion.kt) and the bridge (T383838TakV2Compressor.kt) for any renamed/removed/added wire fields.
4. Test: T383838./gradlew :core:takserver:allTests :core:takserver:compileKotlinJvm (full KMP validation; T383838:core:takserver:jvmTest works as a faster focused check) β against a locally published SDK add T383838-PuseMavenLocal (gated in T383838settings.gradle.kts); against a published version add T383838--refresh-dependencies instead.
Wire facts (don't re-introduce phantom changes): PLI is implicit β no payload variant + an T383838a-f-* cot type is a PLI. T383838DrawnShape vertices are two packed T383838repeated sint32 delta columns. T383838course stays T383838degΓ100, T383838uid stays a string, T383838stale_seconds stays tag 16 β deliberate; do not "fix" them in T383838TAKPacketV2Conversion.kt.
Debug "Send Test CoTs": T383838TakMeshTestRunner sends the bundled T383838tak_test_fixtures/*.xml through the SDK path (parse β strip β compress β send). They ride the SDK path, so they need no edits across wire breaks β they ARE the regression surface.
Dependency Graph
<!--region graph-->
T282828
Te6edf3graph Te6edf3TB
:Te6edf3coreTb4b4b4:Te6edf3takserverTff7b72[Te6edf3takserverTff7b72]Tff7b72:::Te6edf3kmpTff7b72-Te6edf3library
:Te6edf3coreTb4b4b4:Te6edf3takserver T8b949e--> :core:repository
:Te6edf3coreTb4b4b4:Te6edf3takserver Tff7b72-Tb4b4b4.Tff7b72-Tff7b72> :Te6edf3coreTb4b4b4:Te6edf3common
:Te6edf3coreTb4b4b4:Te6edf3takserver Tff7b72-Tb4b4b4.Tff7b72-Tff7b72> :Te6edf3coreTb4b4b4:Te6edf3di
:Te6edf3coreTb4b4b4:Te6edf3takserver Tff7b72-Tb4b4b4.Tff7b72-Tff7b72> :Te6edf3coreTb4b4b4:Te6edf3model
:Te6edf3coreTb4b4b4:Te6edf3takserver Tff7b72-Tb4b4b4.Tff7b72-Tff7b72> :Te6edf3coreTb4b4b4:Te6edf3testing
Te6edf3classDef Te6edf3androidTff7b72-Te6edf3application Te6edf3fillTb4b4b4:Te6edf3#CAFFBFTb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
Te6edf3classDef Te6edf3androidTff7b72-Te6edf3applicationTff7b72-Te6edf3compose Te6edf3fillTb4b4b4:Te6edf3#CAFFBFTb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
Te6edf3classDef Te6edf3composeTff7b72-Te6edf3desktopTff7b72-Te6edf3application Te6edf3fillTb4b4b4:Te6edf3#CAFFBFTb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
Te6edf3classDef Te6edf3androidTff7b72-Te6edf3feature Te6edf3fillTb4b4b4:Te6edf3#FFD6A5Tb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
Te6edf3classDef Te6edf3androidTff7b72-Te6edf3library Te6edf3fillTb4b4b4:Te6edf3#9BF6FFTb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
Te6edf3classDef Te6edf3androidTff7b72-Te6edf3libraryTff7b72-Te6edf3compose Te6edf3fillTb4b4b4:Te6edf3#9BF6FFTb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
Te6edf3classDef Te6edf3androidTff7b72-Te6edf3test Te6edf3fillTb4b4b4:Te6edf3#A0C4FFTb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
Te6edf3classDef Te6edf3jvmTff7b72-Te6edf3library Te6edf3fillTb4b4b4:Te6edf3#BDB2FFTb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
Te6edf3classDef Te6edf3kmpTff7b72-Te6edf3feature Te6edf3fillTb4b4b4:Te6edf3#FFD6A5Tb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
Te6edf3classDef Te6edf3kmpTff7b72-Te6edf3libraryTff7b72-Te6edf3compose Te6edf3fillTb4b4b4:Te6edf3#FFC1CCTb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
Te6edf3classDef Te6edf3kmpTff7b72-Te6edf3library Te6edf3fillTb4b4b4:Te6edf3#FFC1CCTb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
Te6edf3classDef Tff7b72unknown Te6edf3fillTb4b4b4:Te6edf3#FFADADTb4b4b4,Te6edf3strokeTb4b4b4:Te6edf3#000Tb4b4b4,Te6edf3strokeTff7b72-Te6edf3widthTb4b4b4:T79c0ff2Te6edf3pxTb4b4b4,Te6edf3colorTb4b4b4:Te6edf3#000Tb4b4b4;
<!--endregion-->
Served by rngit 1.5.0 - Generated in 0.08s